package com.econage.eccm.test.base.webdatasource.facade;

import com.econage.eccm.test.base.webdatasource.DataSourceConfiguration;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import org.apache.log4j.Logger;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;

public class StatementFacade extends JdbcFacade {
    private static final Logger log = Logger.getLogger(StatementFacade.class);

    protected static final String CLOSE_VAL = "close";
    protected static final String ISCLOSED_VAL = "isClosed";
    protected static final String GETCONNECTION_VAL = "getConnection";


    protected static final String EXECUTE        = "execute";
    protected static final String EXECUTE_QUERY  = "executeQuery";
    protected static final String EXECUTE_UPDATE = "executeUpdate";
    protected static final String EXECUTE_BATCH  = "executeBatch";

    protected static final String[] EXECUTE_TYPES = {EXECUTE, EXECUTE_QUERY, EXECUTE_UPDATE, EXECUTE_BATCH};

    protected static final String GETRESULTSET_VAL  = "getResultSet";
    protected static final String GETMORERESULTS_VAL  = "getMoreResults";
    protected static final String[] CREATE_RS_TYPES = {EXECUTE_QUERY,GETRESULTSET_VAL,GETMORERESULTS_VAL};




    private Statement statement;
    private java.sql.Connection connection;
    private String executeSql;

    private DataSourceConfiguration configuration;


    public StatementFacade(Statement statement, Connection connection, String executeSql, DataSourceConfiguration configuration) {
        this.statement = statement;
        this.connection = connection;
        this.executeSql = executeSql;
        this.configuration = configuration;
    }

    private List<ResultSetEntry> createdResultSet = new LinkedList<ResultSetEntry>();//创建后，未关闭的语句

    @Override
    public Object doInvoke(Object proxy, Method method, Object[] args) throws Throwable {
        //需要拒绝调用的场景
        if(statement==null){
            //已经关闭
            if (compare(ISCLOSED_VAL, method)) {
                return Boolean.TRUE;
            }
            else if (compare(CLOSE_VAL, method)) {
                return null;
            }else {//已经关闭后，忽略任何语句操作
                return null;
            }
        }
        //需要特殊处理的操作
        if (compare(GETCONNECTION_VAL,method)){
            //返回代理连接对象
            return connection;
        }else if(compare(CLOSE_VAL, method)){
            doClose();
            return null;
        }
        //executeQuery特殊处理，执行sql，同时返回结果集
        if(compare(EXECUTE_QUERY,method)){
            ResultSet rs = (ResultSet) invokeExecute(proxy,method,args);
            return resolveResultSet(rs);
        }else if(process(CREATE_RS_TYPES,method)){
            ResultSet rs = (ResultSet)method.invoke(statement,args);
            return resolveResultSet(rs);
        }else if(process(EXECUTE_TYPES,method)){
            return invokeExecute(proxy,method,args);
        }

        return method.invoke(statement,args);
    }

    private String getSQLInRunning(Object proxy, Method method, Object[] args){
        String sql = (Strings.isNullOrEmpty(this.executeSql) && args!=null &&  args.length>0)?(String)args[0]:this.executeSql;
        if (sql==null && compare(EXECUTE_BATCH,method)) {
            sql = "batch";
        }
        return sql;
    }

    private Object invokeExecute(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        boolean shouldReportSlowExecute = configuration.getExecuteSlowThreshold()!=0;
        if(!shouldReportSlowExecute){
            return method.invoke(statement,args);
        }

        //报告执行缓慢的信息
        long start = System.currentTimeMillis();
        Object rs = method.invoke(statement,args);
        long delta = System.currentTimeMillis()-start;
        if (delta>configuration.getExecuteSlowThreshold()) {
            log.warn("Slow Query Report SQL="+getSQLInRunning(proxy,method,args)+"; time="+delta+" ms;");
        }
        return rs;
    }

    private ResultSet resolveResultSet(ResultSet rs){
        ResultSetEntry rse = new ResultSetEntry(rs);
        createdResultSet.add(rse);
        return rs;
    }

    private void doClose(){
        if(statement==null){
            return;
        }
        try{
            //回收结果集
            while (createdResultSet.size()>0) {
                ResultSetEntry ws = createdResultSet.remove(0);
                ResultSet rs = ws.getResultSet();
                boolean shallClose = false;
                try {
                    shallClose = rs!=null && (!rs.isClosed());
                    if (shallClose) {
                        rs.close();
                    }
                } catch (Exception ignore) {
                    if (log.isDebugEnabled()) {
                        log.debug("Unable to closed ResultSet upon statement close.",ignore);
                    }
                } finally {
                    if (shallClose&&configuration.isStatementLeakWaring()) {
                        log.warn("ResultSet created, but was not closed at:", ws.getAllocationStack());
                    }
                }
            }
        }finally{
            //调用底层资源，可能会报错，finall块确保内部状态正常回收
            try {
                this.statement.close();
            }catch (SQLException ignore) {
            }finally{
                this.connection = null;
                this.statement = null;
            }
        }
    }



    protected class ResultSetEntry {
        private ResultSet resultSet;
        private Throwable allocationStack;

        public ResultSetEntry(ResultSet resultSet) {
            this.resultSet = resultSet;
            if (StatementFacade.this.configuration.isTraceResultSetCreation()) {
                this.allocationStack = new Throwable();
            }
        }

        public ResultSet getResultSet() {
            return resultSet;
        }

        public Throwable getAllocationStack() {
            return allocationStack;
        }
    }

    @Override
    public String toString(){
        return MoreObjects.toStringHelper(this)
                .omitNullValues()
                .add("executeSql",executeSql)
                .add("connection",connection)
                .add("statement",statement)
                .toString();
    }
}
